package net.oschina.bilbodai.common.retrofitor.support;

import net.oschina.bilbodai.common.retrofitor.ClassPathBeanScanner;
import net.oschina.bilbodai.common.retrofitor.annotation.HttpService;
import net.oschina.bilbodai.common.retrofitor.core.IGlobalURLProvider;
import net.oschina.bilbodai.common.retrofitor.core.IURLProvider;
import net.oschina.bilbodai.common.retrofitor.core.RetrofitorFactoryBean;
import net.oschina.bilbodai.common.retrofitor.support.parse.ContextUtils;
import okhttp3.OkHttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.type.classreading.MetadataReader;
import retrofit2.CallAdapter;
import retrofit2.Converter;

import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

/**
 * @author daimao  Date: 15/12/30 Time: 下午5:54
 * @version $Id$
 */
public class ConfigurationContext implements BeanDefinitionRegistryPostProcessor, PriorityOrdered {

    private static final Logger LOGGER = LoggerFactory.getLogger(ConfigurationContext.class);
    private BeanDefinitionRegistry registry;
    private String scanPackage;
    private IGlobalURLProvider globalURLProvider = null;

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        this.registry = registry;
    }

    @Override
    public void postProcessBeanFactory(final ConfigurableListableBeanFactory beanFactory) throws BeansException {

        String[] globalURLProviderBeanNames = beanFactory.getBeanNamesForType(IGlobalURLProvider.class);
        if (globalURLProviderBeanNames != null && globalURLProviderBeanNames.length == 1) {
            globalURLProvider = (IGlobalURLProvider) beanFactory.getBean(globalURLProviderBeanNames[0]);
        } else {
            StringBuilder sb = new StringBuilder();
            sb.append("[");
            if (globalURLProviderBeanNames != null) {
                for (String globalURLProviderBeanName : globalURLProviderBeanNames) {
                    sb.append(globalURLProviderBeanName).append(",");
                }
            }
            sb.append("]");
            LOGGER.info("Unable to find available GlobalURLProvider, make sure there exists only one in spring context, but found " + sb.toString());
        }

        ClassPathBeanScanner scanner = new ClassPathBeanScanner() {
            @Override
            public void onCandidateFound(BeanDefinition beanDefinition) throws Exception {

                String beanClassName = beanDefinition.getBeanClassName();
                Class<?> targetClass = Class.forName(beanClassName);
                HttpService httpService = targetClass.getAnnotation(HttpService.class);
                String baseUrl = httpService.baseUrl();
                Class<? extends IURLProvider> urlProviderType = httpService.urlProvider();

                String validBaseUrl = getValidBaseUrl(targetClass, baseUrl, urlProviderType, beanFactory);

                OkHttpClient httpClient = getHttpClient(targetClass, httpService, beanFactory);

                Set<Converter.Factory> converterFactorySet = getConverterFactories(httpService, beanFactory);

                Set<CallAdapter.Factory> adapterFactorySet = getAdapterFactories(httpService, beanFactory);

                BeanDefinition proxyBeanDefinition = ContextUtils.createSingletonDefinition(RetrofitorFactoryBean.class);
                MutablePropertyValues propertyValues = proxyBeanDefinition.getPropertyValues();
                propertyValues.add("baseUrl", validBaseUrl);
                propertyValues.add("targetType", targetClass);
                propertyValues.add("okHttpClient", httpClient);
                propertyValues.add("converterFactorySet", converterFactorySet);
                propertyValues.add("adapterFactorySet", adapterFactorySet);
                propertyValues.add("useDefaultConverters", httpService.useDefaultConverters());

                registry.registerBeanDefinition(beanClassName, proxyBeanDefinition);
            }

            @Override
            public boolean isCandidate(MetadataReader metadataReader) {
                return metadataReader.getAnnotationMetadata().hasAnnotation(HttpService.class.getName());
            }
        };
        scanner.doScan(scanPackage);
    }

    private OkHttpClient getHttpClient(Class<?> targetClass, HttpService httpService, ConfigurableListableBeanFactory beanFactory) {
        OkHttpClient httpClient = null;
        String httpClientRef = httpService.httpClientRef();
        //如果httpClientRef设置了,则优先采用对应的bean
        if (httpClientRef != null && httpClientRef.length() > 0) {
            try {
                httpClient = (OkHttpClient) beanFactory.getBean(httpClientRef);
            } catch (BeansException e) {
                throw new RuntimeException("can't find " + httpClientRef + " bean in spring context");
            }
        } else {
            //否则检查是否开启httpClientAutoInject标识
            boolean clientAutoInject = httpService.httpClientAutoInject();
            if (clientAutoInject) {
                //如果开启了httpClientAutoInject标识,则从上下文中获取OkHttpClient实例
                String[] globalHttpClientBeanNames = beanFactory.getBeanNamesForType(OkHttpClient.class);
                if (globalHttpClientBeanNames == null || globalHttpClientBeanNames.length == 0) {//如果不存在任何实例,则报错
                    throw new RuntimeException("can't find any global OKHttpClient bean in spring context");
                } else if (globalHttpClientBeanNames.length > 1) {//如果存在多个,则报错
                    StringBuilder sb = new StringBuilder();
                    sb.append("[");
                    for (String clientBeanName : globalHttpClientBeanNames) {
                        sb.append(clientBeanName).append(",");
                    }
                    sb.append("]");
                    throw new RuntimeException("found multiple global OKHttpClient bean in spring context " + sb.toString());
                }
                //使用那个实例
                httpClient = (OkHttpClient) beanFactory.getBean(globalHttpClientBeanNames[0]);
            } else {
                //使用默认创建方式,这个会在RetrofitorFactoryBean中体现
                LOGGER.info("Creating a default OKHttpClient for " + targetClass);
            }
        }
        return httpClient;
    }

    private String getValidBaseUrl(Class<?> targetClass, String baseUrl, Class<? extends IURLProvider> urlProviderType, ConfigurableListableBeanFactory beanFactory) {
        String validBaseUrl;//如果设置了baseUrl,则优先采用设置的
        if (baseUrl != null && baseUrl.length() > 0) {
            validBaseUrl = baseUrl;
        } else if (urlProviderType != IURLProvider.class) {//如果设置了自定义的IURLProvider
            try {
                //首先从上下文中获取
                IURLProvider provider = beanFactory.getBean(urlProviderType);
                //获取到则拿到url
                String url = provider.getURL();
                if (url == null) {
                    throw new RuntimeException("Failed to create " + targetClass + " cause Illegal 'null' url provided!");
                }
                validBaseUrl = url;
            } catch (BeansException e) {
                //否则尝试直接初始化urlProviderType
                LOGGER.info("Failed find URLProvider in Spring Context, try direct initialize");
                try {
                    String url = urlProviderType.newInstance().getURL();
                    if (url == null) {
                        throw new RuntimeException("Failed to create " + targetClass + " cause Illegal 'null' url provided!");
                    }
                    validBaseUrl = url;
                } catch (Exception e1) {
                    throw new RuntimeException("Failed to create " + targetClass + " cause Illegal IURLProvider provided!", e);
                }
            }
        } else {//否则查找全局的globalURLProvider
            if (globalURLProvider == null) {
                throw new RuntimeException("Failed to create " + targetClass + " cause No baseUrl or URLProvider or GlobalURLProvider provided!");
            }
            String urlForGlobal = globalURLProvider.getURL(targetClass);
            if (urlForGlobal == null) {
                throw new RuntimeException("Failed to create " + targetClass + " cause Illegal 'null' GlobalURL provided!");
            }
            validBaseUrl = urlForGlobal;
        }
        return validBaseUrl;
    }

    private Set<CallAdapter.Factory> getAdapterFactories(HttpService httpService, ConfigurableListableBeanFactory beanFactory) {
        Set<CallAdapter.Factory> adapterFactorySet = new LinkedHashSet<>();
        List<String> availableAdapterNames = new LinkedList<>();
        if (httpService.adapterAutoInject()) {
            String[] namesForType = beanFactory.getBeanNamesForType(CallAdapter.Factory.class);
            if (namesForType != null) {
                availableAdapterNames.addAll(Arrays.asList(namesForType));
            }
        }
        String[] adapterFactoryNames = httpService.adapterFactories();
        if (adapterFactoryNames != null) {
            availableAdapterNames.addAll(Arrays.asList(adapterFactoryNames));
        }
        for (String adapterFactoryName : availableAdapterNames) {
            adapterFactorySet.add((CallAdapter.Factory) beanFactory.getBean(adapterFactoryName));
        }
        return adapterFactorySet;
    }

    private Set<Converter.Factory> getConverterFactories(HttpService httpService, ConfigurableListableBeanFactory beanFactory) {
        Set<Converter.Factory> converterFactorySet = new LinkedHashSet<>();
        List<String> availableConverterNames = new LinkedList<>();
        if (httpService.convertersAutoInject()) {
            String[] namesForType = beanFactory.getBeanNamesForType(Converter.Factory.class);
            if (namesForType != null) {
                availableConverterNames.addAll(Arrays.asList(namesForType));
            }
        }
        String[] converterFactoryNames = httpService.converterFactories();
        if (converterFactoryNames != null) {
            availableConverterNames.addAll(Arrays.asList(converterFactoryNames));
        }
        for (String availableConverterName : availableConverterNames) {
            converterFactorySet.add((Converter.Factory) beanFactory.getBean(availableConverterName));
        }
        return converterFactorySet;
    }

    @Override
    public int getOrder() {
        return HIGHEST_PRECEDENCE;
    }

    public String getScanPackage() {
        return scanPackage;
    }

    public void setScanPackage(String scanPackage) {
        this.scanPackage = scanPackage;
    }
}